#include <arch.h>
#include <t8.h>
#include <config.h>
#include <errno.h>
#include <serial.h>
#include <types.h>
#include <time.h>
/* number of nibbles in a word */
#define NIBBLES_PER_WORD (8)
#define NIBBLES_PER_HALFWORD (4)

int whichUart = 0; // BT UART
int pclk = 0;

// which = 0, 1, 2
void SerialSelect(int which)
{
    whichUart = which;
}

#define Baudrate (PCLK/16./115200+0.5) // temp
#define UART1_Baudrate (PCLK/16./460800+0.5) // temp
void SerialInit(serial_baud_t baudrate)
{
    int i;
    if(pclk == 0) pclk    = PCLK;

    rUFCON0 = 0x0;   //UART channel 0 FIFO control register, FIFO disable
    rUFCON1 = 0x51;//0x0;   //UART channel 1 FIFO control register, FIFO disable
    rUFCON2 = 0x0;   //UART channel 2 FIFO control register, FIFO disable
    rUMCON0 = 0x0;   //UART chaneel 0 MODEM control register, AFC disable
    rUMCON1 = 0x0;   //UART chaneel 1 MODEM control register, AFC disable
//UART0
    rULCON0 = 0x3;   //Line control register : Normal,No parity,1 stop,8 bits
     //    [10]       [9]     [8]        [7]        [6]      [5]         [4]           [3:2]        [1:0]
     // Clock Sel,  Tx Int,  Rx Int, Rx Time Out, Rx err, Loop-back, Send break,  Transmit Mode, Receive Mode
     //     0          1       0    ,     0          1        0           0     ,       01          01
     //   PCLK       Level    Pulse    Disable    Generate  Normal      Normal        Interrupt or Polling
    rUCON0  = 0x245;   // Control register
    rUBRDIV0=( (int)(Baudrate) -1 );   //Baud rate divisior register 0

//UART1

    rULCON1 = 0x3;
    rUCON1  = 0x245;
    rUBRDIV1=( (int)(UART1_Baudrate) -1 );
    //rUBRDIV1=( (int)(pclk/16./baud+0.5) -1 );
//    rUMCON1 = 0x10; // Enable AFC
//UART2
    rULCON2 = 0x3;
    rUCON2  = 0x245;
    rUBRDIV2=( (int)(Baudrate) -1 );
    //rUBRDIV2=( (int)(pclk/16./baud+0.5) -1 );

    for(i=0;i<100;i++);

    whichUart = 0;
}

void SerialOutputByte(const unsigned char c)
{
  if(c == '\n')
    SerialOutputRawByte('\r');

  SerialOutputRawByte(c);
}

/*
 * Output a single byte to the serial port.
 */
void SerialOutputRawByte(const unsigned char c)
{
    if(whichUart==0)
    {
        if(c=='\n')
        {
            while(!(rUTRSTAT0 & 0x2));
            ostdelay(10);                 //because the slow response of hyper_terminal
            WrUTXH0('\r');
        }
        while(!(rUTRSTAT0 & 0x2));   //Wait until THR is empty.
        ostdelay(10);
        WrUTXH0(c);
    }
    else if(whichUart==1)
    {
        if(c=='\n')
        {
            while(!(rUTRSTAT1 & 0x2));
            ostdelay(10);                 //because the slow response of hyper_terminal
            rUTXH1 = '\r';
        }
        while(!(rUTRSTAT1 & 0x2));   //Wait until THR is empty.
        ostdelay(10);
        rUTXH1 = c;
    }
    else if(whichUart==2)
    {
        if(c=='\n')
        {
            while(!(rUTRSTAT2 & 0x2));
            ostdelay(10);                 //because the slow response of hyper_terminal
            rUTXH2 = '\r';
        }
        while(!(rUTRSTAT2 & 0x2));   //Wait until THR is empty.
        ostdelay(10);
        rUTXH2 = c;
    }
}

void SerialOutputByteInHex(const char c)
{
  char d;

  d = (char)((c >> 4) & 0x0f);
  if(d > 9)
    d += ('A' - 10);
  else
    d += '0';
  SerialOutputByte(d);

  d = (char)(c & 0x0f);
  if(d > 9)
    d += ('A' - 10);
  else
    d += '0';
  SerialOutputByte(d);
}

/*
 * Write a null terminated string to the serial port.
 */
void SerialOutputString(const char *s)
{
    while(*s != 0) {
        SerialOutputByte(*s++);
    }
}

/*
 * Write the argument of the function in hexadecimal to the serial
 * port. If you want "0x" in front of it, you'll have to add it
 * yourself.
 */
void SerialOutputHex(const u32 h)
{
    char c;
    int i;

    for(i = NIBBLES_PER_WORD - 1; i >= 0; i--) {
        c = (char)((h >> (i * 4)) & 0x0f);

        if(c > 9)
            c += ('A' - 10);
        else
            c += '0';

        SerialOutputByte(c);
    }
}

void SerialOutputSHex(const u32 h)
{
    char c;
    int i;

    for(i = NIBBLES_PER_HALFWORD - 1; i >= 0; i--) {
        c = (char)((h >> (i * 4)) & 0x0f);

        if(c > 9)
            c += ('A' - 10);
        else
            c += '0';

        SerialOutputByte(c);
    }
}

/*
 * Write the argument of the function in decimal to the serial port.
 * We just assume that each argument is positive (i.e. unsigned).
 */
void SerialOutputDec(const u32 d)
{
    int leading_zero = 1;
    u32 divisor, result, remainder;

    remainder = d;

    for(divisor = 1000000000;
        divisor > 0;
        divisor /= 10) {
        result = remainder / divisor;
        remainder %= divisor;

        if(result != 0 || divisor == 1)
            leading_zero = 0;

        if(leading_zero == 0)
            SerialOutputByte((char)(result) + '0');
    }
}

/*
 * Read a single byte from the serial port. Returns 1 on success, 0
 * otherwise. When the function is succesfull, the character read is
 * written into its argument c.
 */
int SerialInputStr(char *buf, int count)
{
#if 0
    char *string2 = buf;
    char c;
    while((c = SerialInputByte())!='\r')
    {
        if(c=='\b')
        {
            if( (int)string2 < (int)buf )
            {
                printk("\b \b");
                string--;
            }
        }
        else
        {
            *buf++ = c;
            SerialOutputByte(c);
        }
    }
    *buf='\0';
    SerialOutputByte('\n');
    #endif
        return 1;
} /* SerialInputByte */

/*
 * Write a block of data to the serial port. Similar to
 * SerialOutputString(), but this function just writes the number of
 * characters indicated by bufsize and doesn't look at termination
 * characters.
 */
void SerialOutputBlock(const char *buf, int bufsize)
{
    while(bufsize--)
        SerialOutputByte(*buf++);
}

/*
 * Read a single byte from the serial port. Returns 1 on success, 0
 * otherwise. When the function is succesfull, the character read is
 * written into its argument c.
 */
int SerialInputByte(char *c)
{
    if(whichUart==0)
    {
        if(rUTRSTAT0 & 0x1)  {
            //Receive data ready
            *c = RdURXH0();
            return 1;
        }
    }
    else if(whichUart==1)
    {
        if(rUTRSTAT1 & 0x1)    {
            //Receive data ready
            *c = RdURXH1();
            return 1;
        }
    }
    else if(whichUart==2)
    {
        if(rUTRSTAT2 & 0x1)    {
            //Receive data ready
            *c = RdURXH2();
            return 1;
        }
    }

    return 0;

} /* SerialInputByte */

/*
 * read a string with maximum length len from the serial port
 * using a timeout of timeout seconds
 *
 * len is the length of array s _including_ the trailing zero,
 * the function returns the number of bytes read _excluding_
 * the trailing zero
 */
int  SerialInputString(char *s, const int len, const int timeout)
{
    u32 startTime, currentTime;
    char c;
    int i;
    int numRead;
    int skipNewline = 1;
    int maxRead = len - 1;

    startTime = TimerGetTime();

    for(numRead = 0, i = 0; numRead < maxRead;) {
        /* try to get a byte from the serial port */
        while(!SerialInputByte(&c)) {
            currentTime = TimerGetTime();

            /* check timeout value */
            if((currentTime - startTime) > timeout ) {
                /* timeout */
                s[i++] = '\0';
                return(numRead);
            }
        }
        startTime = TimerGetTime();
        /* eat newline characters at start of string */
        if((skipNewline == 1) && (c != '\r') && (c != '\n'))
            skipNewline = 0;

        if(skipNewline == 0) {
            if((c == '\r') || (c == '\n')) {
                s[i++] = '\0';
                return(numRead);
            } else {
                s[i++] = c;
                numRead++;
            }
        }
    }

    return(numRead);
}

/*
 * SerialInputBlock(): almost the same as SerialInputString(), but
 * this one just reads a block of characters without looking at
 * special characters.
 */
int  SerialInputBlock(char *buf, int bufsize, const int timeout)
{
    u32 startTime, currentTime;
    char c;
    int i;
    int numRead;
    int maxRead = bufsize;

    startTime = TimerGetTime();

    for(numRead = 0, i = 0; numRead < maxRead;) {
      /* try to get a byte from the serial port */
        while(!SerialInputByte(&c)) {
            currentTime = TimerGetTime();

            /* check timeout value */
            if((currentTime - startTime) > timeout) {
                /* timeout! */
                return(numRead);
            }
        }
//      startTime = TimerGetTime();
        buf[i++] = c;
        numRead ++;
    }

    return(numRead);
}

int SerialInputBlock_msec(char *buf, int bufsize, const int timeout_msec)
{
    char c;
    int i;
    int numRead;
    int maxRead = bufsize;
    int timeCnt = timeout_msec * 10000;

    for(numRead = 0, i = 0; numRead < maxRead;) {
      /* try to get a byte from the serial port */
        while(!SerialInputByte(&c)) {
            /* check timeout value */
            if(timeCnt-- <= 0) {
                /* timeout! */
                // If timeout_msec == 0, waiting for read withour timeout.
                if (timeout_msec) return numRead;
            }
        }
//      startTime = TimerGetTime();
        buf[i++] = c;
        numRead ++;
        //timeCnt = timeout_msec * 10000;
    }

    return(numRead);
}

void SerialInputFlush(void)
{
}

int SerialInputDWordWithTimeout()
{
    int nRet;

    if (SerialInputBlock_msec( (char *)&nRet, 4, 10) < 4) {
        return -1;
    }
    else {
        return nRet;
    }
}

void Xo_SerialOutputByte(const unsigned char c)
{
    SerialOutputByte(c);
}

int Xo_SerialInputByte(char *c)
{
    if( SerialInputByte(c))
        return 0;
    return -1;
}
